/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cliente;

import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import aso.upna.net.*;
import aso.upna.playlist.GenericList;
import java.io.EOFException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 *
 * @author triki
 */

public class ClientThread implements Runnable {

    private Socket sock;
    private ConcurrentHashMap u_lookup;
    private ConcurrentHashMap u_reg;
    private int current_state;
    private GenericList u_list;

    private String shared_path;

    private UserInfo u_info;

    private boolean reject;

    private ObjectInputStream ois;
    private ObjectOutputStream oos;

    private final int NO_STATE = -1;
    private final int LOGIN_STATE = 0;
    private final int SHARED_STATE = 1;
    private final int CONNECTED_STATE = 2;
    private final int REJECTED_STATE = 3;
    private final int ERROR_STATE = 9;

    public ClientThread(Socket sock,ConcurrentHashMap u_lookup,ConcurrentHashMap u_reg,String path)
    {
        this.sock = sock;
        this.u_lookup = u_lookup;
        this.u_reg = u_reg;
        u_info = null;
        u_list = null;
        current_state = LOGIN_STATE;
        reject = false;
        shared_path = new String(path);
    }

    public void registerUserlist(GenericList list)
    {
        u_list = list;
    }

    public void rejectUser()
    {
        current_state = REJECTED_STATE;
    }

    //Cuerpo del hilo.
    public void run()
    {
        Object aux = null;

        try
        {
            System.out.println(">> abriendo streams...");
            oos = new ObjectOutputStream(sock.getOutputStream());
            ois = new ObjectInputStream(sock.getInputStream());
            
            System.out.println(">>[OK]");

            boolean end = false;

            while(!end)
            {
                switch(current_state)
                {
                    case LOGIN_STATE:
                        loginState();
                    break;
                    case SHARED_STATE:
                        sharedState();
                    break;
                    case CONNECTED_STATE:
                        connectedState();
                    break;
                    case REJECTED_STATE:
                        rejectedState();
                    break;
                    default:
                        end = true;
                }
            }

            this.disconnect();
            return;
        }
        catch (IOException ex)
        {
           ex.printStackTrace();
           disconnect();
           return;
        }

       
    }

    //En esta clase solo se atenderan las peticiones relacionadas con el login.
    public void loginState()
    {
        try
        {
            Object aux = ois.readObject();

            if(aux instanceof WellcomeMessage)
            {
                WellcomeMessage msg = (WellcomeMessage) aux;

                String user_id = msg.getUser();
                String ip = sock.getInetAddress().getHostAddress();
                int port = msg.getTcpPort();
                
                u_info = new UserInfo(user_id,ip,port);

                System.out.println("*** LOGIN INFO ***");
                System.out.println("Userid: "+user_id);
                System.out.println("IP: "+ip);
                System.out.println("puerto: "+port);

                //metemos a la tabla hash la informacion.

                if(u_lookup.get(user_id) != null) //ya existe el usuario.
                {
                    String message = "Ya hay un usuario logueado con ese usuario.";
                    RespondMessage res = new RespondMessage(message,1002);
                    oos.writeObject(res);
                    current_state = ERROR_STATE;
                }
                else
                {
                    u_lookup.put(user_id, u_info);
                    if(u_list != null)
                        u_list.addElement(u_info);

                    //Enviamos un OK
                    RespondMessage res = new RespondMessage("Ok",0);
                    oos.writeObject(res);
                    
                    current_state = SHARED_STATE;
                }
            }
            else
            {
                RespondMessage res = new RespondMessage("funcion no valida",2);
                oos.writeObject(res);
            }
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
        catch (ClassNotFoundException ex)
        {
            ex.printStackTrace();
        }
    }

    private void sharedState()
    {
        try
        {
            Object aux = ois.readObject();
           
            if(aux instanceof SharedSongMessage)
            {
                SharedSongMessage msg = (SharedSongMessage) aux;

                String user_id = msg.getUserId();
                GenericList list = msg.getLista();

                if(list.getCountElements() <= 0)
                {
                    RespondMessage res = new RespondMessage("Lista vacia",3);
                    oos.writeObject(res);

                    current_state = NO_STATE;
                    return;
                }
                
                //metemos a la tabla hash la informacion.
                u_reg.put(user_id, list);
                store(user_id,list);
                
                //Enviamos un OK
                RespondMessage res = new RespondMessage("Ok",0);
                oos.writeObject(res);
                current_state = CONNECTED_STATE;
            }
            else
            {
                RespondMessage res = new RespondMessage("funcion no valida",2);
                oos.writeObject(res);
            }
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
        catch (ClassNotFoundException ex)
        {
            ex.printStackTrace();
        }
    }

    private void connectedState()
    {
        try
        {
            Object aux = ois.readObject();

            if(aux instanceof SearchQueryMessage)//mensaje de busqueda.
            {
                SearchQueryMessage msg = (SearchQueryMessage)aux;
                String pattern = msg.getPattern();
                GenericList lista = Search.search_by_pattern(u_reg, pattern);

                GenericListMessage mess = new GenericListMessage(lista);
                oos.writeObject(mess);
                return;
            }

            if(aux instanceof LookUpMessage)
            {
                LookUpMessage msg = (LookUpMessage)aux;
                UserInfo info = (UserInfo) u_lookup.get(msg.getUserId());
                //Si se ha encontrado en la tabla hash...
                if(info != null)
                {
                    UserInfoMessage res = new UserInfoMessage(info.getIp(),info.getPort());
                    oos.writeObject(res);
                    return;
                }
                
                //Sino, envia un mensaje diciendo que no se ha encontrado.
                else
                {
                    RespondMessage res = new RespondMessage("Usuario no encontrado",1000);
                    oos.writeObject(res);
                }
            }
                
            RespondMessage res = new RespondMessage("funcion no valida",2);
            oos.writeObject(res);
            return;
        }
        
        catch(EOFException ex)
        {
            System.out.println("> Desconectando...");
            u_lookup.remove(u_info.getUserId());
            u_reg.remove(u_info.getUserId());
            
            if(u_list != null)
                u_list.remove(u_info);
            
            disconnect();
            current_state = NO_STATE;
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
        catch (ClassNotFoundException ex)
        {
            ex.printStackTrace();
        }
    }

    private void rejectedState()
    {
        try 
        {
            String message = "Se ha alcanzado el numero maximo de usuarios.";
            RespondMessage res = new RespondMessage(message, 1000);
            oos.writeObject(res);
            sock.close();
        }
        catch (IOException ex)
        {
            Logger.getLogger(ClientThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void disconnect()
    {
        try
        {
            sock.close();
        }
        catch (IOException ex)
        {
           ex.printStackTrace();
        }
    }

    private boolean store(String user,GenericList lista)
    {
        FileOutputStream fo = null;
        ObjectOutputStream os = null;
        
        try
        {
            File f = new File(shared_path + user + ".bin");

            if (f.exists())
            {
                f.createNewFile();
            }

            fo = new FileOutputStream(f);
            os = new ObjectOutputStream(fo);

            os.writeObject(lista);
            os.close();

            return true;
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
            return false;
        }
        finally
        {
            try
            {
                fo.close();
            }
            catch (IOException ex)
            {
                return false;
            }
        }
    }
}
